<?php
/**
 * 异常和错误处理类
 *
 * @version $Id$
 * @package if
 * @subpackage util.error
 * @since 1.0
 */
class IErrorHandler extends IObject {
	private $requestMethod;
	
	private $errorLevels = array(E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", E_PARSE => "E_PARSE", E_NOTICE => "E_NOTICE", E_CORE_ERROR => "E_CORE_ERROR", E_CORE_WARNING => "E_CORE_WARNING", E_COMPILE_ERROR => "E_COMPILE_ERROR", E_COMPILE_WARNING => "E_COMPILE_WARNING", E_USER_ERROR => "E_USER_ERROR", E_USER_WARNING => "E_USER_WARNING", E_USER_NOTICE => "E_USER_NOTICE");
	
	/**
	 * 构造器
	 *
	 * @since 1.0
	 */
	function __construct() {
		$this->requestMethod = IRequest::singleton()->method();
		
		//错误级别
		if(defined("E_STRICT")) {
			$this->errorLevels[E_STRICT] = "E_STRICT";
		}
	    if(defined("E_RECOVERABLE_ERROR")) {
	    	$this->errorLevels[E_RECOVERABLE_ERROR] = "E_RECOVERABLE_ERROR";
	    }
	}
	
	/**
	 * 异常处理方法
	 * 
	 * 具体用法见config/_error_default.php
	 *
	 * @param Exception $exception 异常对象
	 * @since 1.0
	 */
	function handlerException($exception) {
		$request = IRequest::singleton();
		$code = "0x" . dechex($exception->getCode());
		$browser = "\n<table border=\"0\" cellspacing=\"1\" bgcolor=\"#eeeeee\" style=\"font:12px 'Courier New'\"><tr><td><strong>I-Framework " . get_class($exception) . " Message:</strong> " . $exception->getMessage() . "<br/>\n</td>\n</tr>\n"
  			  	. "<tr>\n<td>\n<strong>Script:</strong> code <em><strong><a href=\"http://if.4kiki.net/docs/exceptions/{$code}\" target=\"_blank\" title=\"More Information\">" . $code . "</a></strong></em> on line <em><strong>" . $exception->getLine() . "</strong></em> in file <em><strong>" . $exception->getFile() . "</strong></em></td>\n</tr>\n"
  			  	. "<tr>\n<td>\n<strong>URL:</strong> <em>" . $request->uri() . "</em>"
  			  	. "<tr>\n<td>\n" . nl2br($exception->getTraceAsString()) . "</td>\n</tr>\n"
  			  	. "</table>\n";
  		$cmd = "I-Framework " . get_class($exception) . " Message: " . $exception->getMessage() . "\n"
  				. "AT: " . date("Y-m-d H:i:s") . "\n"
  			  	. "code " . $exception->getCode() . " on line " . $exception->getLine() . " in file " . $exception->getFile() . "\n"
  			  	. $exception->getTraceAsString();
  			
  		$exceptionHandlers = o("@error.exception");
  		if (!is_array($exceptionHandlers) || !isset($exceptionHandlers["stdout"]) || $exceptionHandlers["stdout"]) {//stdout
			if ($this->requestMethod == "POST" || $this->requestMethod == "GET") { 
				echo $browser;
			}
			else {
				echo $cmd . "\n";
			}
  		}
  		if (isset($exceptionHandlers["log"]) && $exceptionHandlers["log"] != "") {//write to file
  			$file = IApplication::singleton()->relativeFile($exceptionHandlers["log"])->path();
  			$logWriter = new IFileLogWriter($file);
  			$message = "%{a} - %{t} - %{m} - %{U}\n" . $cmd . "\n\n";
  			IRegistry::singleton()->single("ISimpleLogFilter")->doFilter($message);
  			$logWriter->write($message);
  			$logWriter->close();
  		}
  		if (isset($exceptionHandlers["pages"]) && is_array($exceptionHandlers["pages"])) {//show pages
  			$class = strtolower(get_class($exception));
  			$defined = false;
  			foreach ($exceptionHandlers["pages"] as $_class=>$_page) {
  				if (strtolower($_class) == $class) {
  					$defined = $this->goPage($_page);
  				}
  			}
  			if (!$defined && isset($exceptionHandlers["pages"]["default"])) {
  				$this->goPage($exceptionHandlers["pages"]["default"]);
  			}
  		}
	}
	
	/**
	 * 定义Exception跳转页
	 * 
	 * 支持 
	 * <pre>
	 *  IException => "page1",
	 *  IException => array(
	 * 		"redirect" => "page1"
	 *   ),
	 *  IException => array(
	 * 		"forward" => "page1"
	 *  )
	 * </pre>
	 *
	 * @param string|array $page 页面定义
	 * @since 1.0
	 */
	private function goPage($page) {
		if (is_scalar($page)) {
			$this->goPage(array("forward" => $page));
			return true;
		}
		else if (is_array($page)) {
			if (isset($page["redirect"])) {//redirect
				IResponse::singleton()->redirect($page["redirect"]);
				return true;
			}
			elseif(isset($page["forward"])) {//forward
				$page = $page["forward"];
				if (!if_is_absolute_path($page)) {
					$page = IF_APP_ROOT . "/" . $page;
				}
				if (is_file($page)) {
					require($page);
					return true;
				}
			}
			elseif(isset($page["action"])) {
				$controllerName = null;
				$actionName = null;
				if (strstr($page["action"], ".")) {
					$controllerName = substr($page["action"], 0, strrpos($page["action"], "."));
					$actionName = substr($page["action"], strrpos($page["action"], ".") + 1);
				}
				else {
					$controllerName = $page["action"];
					$actionName = IController::defaultAction();
				}
				$controller = IController::objByName($controllerName);
				if ($controller) {
					if (isset($page["params"]) && is_array($page["params"])) {
						IRequest::singleton()->addParams($page["params"]);
					}
					$controller->setAction($actionName);
					$controller->setRouter(IApplication::singleton()->router());
					$controller->exec();
				}
				else {
					$this->_log("IErrorHandler: can't find controller \"{$controllerName}\"", ILog::LOG_WARNING);
				}
			}
		}
		return false;
	}
	
	/**
	 * 错误处理方法
	 *
	 * @param integer $errorNo 错误号
	 * @param string $errorStr 错误描述字符串
	 * @param string $errorFile 错误发生的文件
	 * @param string $errorLine 错误发生的行号
	 * @param string $errorContext 错误的上下文
	 * @return boolean
	 * @since 1.0
	 */
	function handlerError($errorNo, $errorStr, $errorFile, $errorLine, $errorContext) {
		//get trace
		$trace = null;
		try {
			throw new Exception(null, 0x100107);
		} catch (Exception $e) {
			$trace = $e->getTraceAsString();
		}
		$errorReportingLevel = o("@error.error.level");
		if (error_reporting() == 0) {// mask error by @ operator
			return true;
		}
		
		if ($errorReportingLevel != E_ALL && !in_array($errorNo, $this->errorLevels($errorReportingLevel))) {
			return true;
		}
		if (is_file($errorFile)) {
			$lines = file($errorFile);	
		}
		else {
			$lines = array();
		}
		$description = "";
		$errorIndex = $errorLine - 1;
		$maxLine = 4;
		if ($errorIndex > 0) {
			$preIndex =  max($errorIndex - $maxLine, 0);
			for($lineNo=$preIndex; $lineNo<$errorIndex + $maxLine; $lineNo++) {
				if (isset($lines[$lineNo])) {
					$lines[$lineNo] = $this->format($lines[$lineNo]);
					if ($errorIndex == $lineNo) {
						$description .= "<span style=\"color:red\">" . $lines[$lineNo] . "</span>";
					}
					else {
						$description .= $lines[$lineNo];
					}
				}
			}
		}
		
		$browser = $message = "\n<table border=\"0\" cellspacing=\"1\" bgcolor=\"#eeeeee\" style=\"font:12px 'Courier New'\"><tr><td><strong>I-Framework " . $this->errorLevels[$errorNo] . " Message:</strong> " . $errorStr . "<br/>\n</td>\n</tr>\n"
	  			  . "<tr>\n<td>\n<strong>Script:</strong> code <em><strong>" . $errorNo . "</strong></em> on line <em><strong>" . $errorLine . "</strong></em> in file <em><strong>" . $errorFile . "</strong></em></td>\n</tr>\n"
	  			  . "<tr>\n<td>\n<strong>URL:</strong> <em>" . IRequest::singleton()->uri() . "</em>"
	  			  . "<tr>\n<td><strong>Source:</strong></td></tr>"
	  			  . "<tr>\n<td>-------------------------------------------------------------------</td></tr>"
	  			  . "<tr>\n<td><code>\n" . nl2br($description) . "</code></td>\n</tr>\n"
	  			  . "<tr>\n<td>-------------------------------------------------------------------</td></tr>"
	  			  . "<tr>\n<td>" . nl2br($trace) . "</td>\n</tr>\n"
	  			  . "</table>\n";
	  	$cmd = "I-Framework " . $this->errorLevels[$errorNo]  . " Message: " . $errorStr . "\n" .
	  				"AT: " . date("Y-m-d H:i:s") . "\n"
	  			  . "code " . $errorNo . " on line " . $errorLine . " in file " . $errorFile . "\n" . $trace . "\n";
	
		if (o("@error.error.stdout")) {
			if ($this->requestMethod == "POST" || $this->requestMethod == "GET") { 
				echo $browser;
			}
			else {
				echo $cmd;
			}
		}
		
		//logfile
		$logFile = o("@error.error.log");
		if ($logFile) {
			$logFile = IApplication::singleton()->relativeFile($logFile)->path();
  			$logWriter = new IFileLogWriter($logFile);
  			$logWriter->write($cmd . "\n");
  			$logWriter->close();
		}
		return true;
	}
	
	/**
	 * 格式化文本
	 *
	 * @param unknown_type $text
	 * @return unknown
	 * @since 1.0
	 */
	private function format($text) {
		$text = htmlspecialchars($text);
		$text = str_replace(" ", "&nbsp;", $text);
		$text = str_replace("	", " &nbsp; &nbsp;", $text);
		return $text;
	}
	
	/**
	 * 根据级别取得支持的错误级别
	 *
	 * @param integer $level 错误级别
	 * @return array
	 * @since 1.0
	 */
	private function errorLevels($level) {
	    $names = array_keys($this->errorLevels);
	    $levels = array();
	    if(($level & E_ALL) == E_ALL) {
	        $levels[] = E_ALL;
	        $level &= ~E_ALL;
	    }
	    foreach($names as $name) {
	        if(($level & $name) == $name) {
	        	$levels[] = $name;
	        }
	    }
	    return $levels;
	}
}


?>